/**
 * bmp 文件解析
 */
const fs = require("fs");

const bmpFile = fs.readFileSync("884258071.bmp");

console.log(bmpFile);

// 将大端位序的字节转换为小端位序的字节
const reverseBytes = (bytes) => {
  const len = bytes.length;
  const reversed = Buffer.alloc(len);
  for (let i = 0; i < len; i++) {
    reversed[i] = bytes[len - i - 1];
  }
  return reversed;
};

// BMP数据从文件头开始的先后顺序分别为：文件头、位图信息头、调色板、位图数据
/**
 * 文件头：提供文件的格式、大小等信息，共14字节。
 */
// 文件类型 2 bytes
const bfType = reverseBytes(bmpFile.slice(0, 2)).toString("hex");
console.log(bfType);
// 位图大小 4 bytes
const bfSize = reverseBytes(bmpFile.slice(2, 6)).toString("hex");
console.log(bfSize);
// 保留位，必须设置0 2 bytes
const bfReserved1 = reverseBytes(bmpFile.slice(6, 8)).toString("hex");
console.log(bfReserved1);
// 保留位，必须设置0 2 bytes
const bfReserved2 = reverseBytes(bmpFile.slice(8, 10)).toString("hex");
console.log(bfReserved2);
// 说明从文件头开始到实际的图像数据之间的字节的偏移量 4 bytes
const bf0ffBits = reverseBytes(bmpFile.slice(10, 14)).toString("hex");
console.log(bf0ffBits);

/**
 * 位图信息头：提供位图的尺寸、位深度、压缩方式等信息，共40字节。
 */
// biSize   4 bytes	描述信息头结构体所需的字节
const biSize = reverseBytes(bmpFile.slice(14, 18)).toString("hex");
console.log(biSize);
// biWidth	4 bytes	位图的宽度，以像素为单位
const biWidth = reverseBytes(bmpFile.slice(18, 22)).toString("hex");
console.log('biWidth', biWidth);
console.log(parseInt(biWidth, 16));
// biHeight	4 bytes	位图的高度，以像素为单位
const biHeight = reverseBytes(bmpFile.slice(22, 26)).toString("hex");
console.log('biHeight', biHeight);
// biPlanes	2 bytes	说明颜色平面数，总被设置为1
const biPlanes = reverseBytes(bmpFile.slice(26, 28)).toString("hex");
console.log(biPlanes);
// biBitCount	2 bytes	说明比特数/像素，常用的值为1、4、8、16、24、32
const biBitCount = reverseBytes(bmpFile.slice(28, 30)).toString("hex");
// 如果是24位的bmp，因此没有调色板数据，文件头和信息头之后就是真正的图像数据。
console.log('biBitCount', biBitCount);
console.log(parseInt(biBitCount, 16));
// biCompression	4 bytes	说明图像数据压缩的类型，0为不压缩
const biCompression = reverseBytes(bmpFile.slice(30, 34)).toString("hex");
console.log(biCompression);
// biSizeImage	4 bytes	说明图像大小，以字节为单位
const biSizeImage = reverseBytes(bmpFile.slice(34, 38)).toString("hex");
console.log(biSizeImage);
// biXPelsPerMeter	4 bytes	说明水平分辨率，以像素/米为单位
const biXPelsPerMeter = reverseBytes(bmpFile.slice(38, 42)).toString("hex");
console.log(biXPelsPerMeter);
// biYPelsPerMeter	4 bytes	说明垂直分辨率，以像素/米为单位
const biYPelsPerMeter = reverseBytes(bmpFile.slice(42, 46)).toString("hex");
console.log(biYPelsPerMeter);
// biClrUsed	4 bytes	说明位图实际使用的颜色表中的颜色索引数
const biClrUsed = reverseBytes(bmpFile.slice(46, 50)).toString("hex");
console.log(biClrUsed);
// biClrImportant	4 bytes	说明对图像显示有重要影响的颜色索引的数目，如果是0，表示都重要
const biClrImportant = reverseBytes(bmpFile.slice(50, 54)).toString("hex");
console.log(biClrImportant);

/**
 * 调色板：提供位图中使用到的颜色信息，共1024字节。
 */
// 调色板数据，如果是24位的bmp，因此没有调色板数据，文件头和信息头之后就是真正的图像数据。
// const colorTable = bmpFile.slice(54, 1078);
// console.log(colorTable);

/**
 * 位图数据：提供位图的像素信息，共n字节。
 */
// 位图数据，如果是24位的bmp，因此没有调色板数据，文件头和信息头之后就是真正的图像数据。
const bitmapData = bmpFile.slice(parseInt(bf0ffBits, 16));
console.log(bitmapData);

lineByteCnt =
  ((parseInt(biBitCount, 16) * parseInt(biWidth, 16) + 31) >> 5) << 2;
console.log('lineByteCnt', lineByteCnt);

const bitmapDataArr = [];
for (let i = 0; i < parseInt(biHeight, 16); i++) {
  bitmapDataArr.unshift(
    bitmapData.slice(i * lineByteCnt, (i + 1) * lineByteCnt)
  );
}
// console.log(bitmapDataArr);
// 转换成 rgb 值
const rgbArr = [];
for (let i = 0; i < bitmapDataArr.length; i++) {
  const line = bitmapDataArr[i];
  const lineArr = [];
  for (let j = 0; j < line.length; j += 3) {
    lineArr.push([line[j], line[j + 1], line[j + 2]]);
  }
  rgbArr.push(lineArr);
}
// console.dir(rgbArr);

const koa = require("koa");
const app = new koa();

app.use(async (ctx) => {
  ctx.body = `<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>bmp</title>
    </head>
    <body>
      <div style="display: flex;justify-content: space-around;">
        <div>
          <h1>canvas 完整渲染</h1>
          <canvas id="canvas" width="${parseInt(
            biWidth,
            16
          )}" height="${parseInt(biHeight, 16)}"></canvas>
        </div>
        <div>
          <h1>div逐行渲染</h1>
          <div id="img-box"></div>
        </div>
      </div>
      <script>
        const canvas = document.getElementById("canvas");
        const ctx = canvas.getContext("2d");
        const imageData = ctx.createImageData(${parseInt(
          biWidth,
          16
        )}, ${parseInt(biHeight, 16)});
        const data = imageData.data;
        const rgbArr = ${JSON.stringify(rgbArr)};
        for (let i = 0; i < rgbArr.length; i++) {
            const line = rgbArr[i];
            for (let j = 0; j < line.length; j++) {
                const rgb = line[j];
                data[i * ${parseInt(biWidth, 16)} * 4 + j * 4] = rgb[2];
                data[i * ${parseInt(biWidth, 16)} * 4 + j * 4 + 1] = rgb[1];
                data[i * ${parseInt(biWidth, 16)} * 4 + j * 4 + 2] = rgb[0];
                data[i * ${parseInt(biWidth, 16)} * 4 + j * 4 + 3] = 255;
            }
        }
        ctx.putImageData(imageData, 0, 0);
      </script>
      
      <script>
        const imgBox = document.getElementById("img-box");
        console.log(rgbArr.length);
        function raf() {
          if (rgbArr.length) {
            const line = rgbArr.shift();
            const lineDiv = document.createElement("div");
            lineDiv.style.height = "1px";
            lineDiv.style.width = "100%";
            lineDiv.style.display = "flex";
            for (let i = 0; i < line.length; i++) {
              const rgb = line[i];
              const div = document.createElement("div");
              div.style.width = "1px";
              div.style.height = "100%";
              div.style.backgroundColor = \`rgb(\${rgb[2]}, \${rgb[1]}, \${rgb[0]})\`;
              lineDiv.appendChild(div);
            }
            imgBox.appendChild(lineDiv);
            requestAnimationFrame(raf);
          }
        }
        requestAnimationFrame(raf);
      </script>
    </body>
    </html>
`;
});
app.listen(8000, () => {
  console.log("server is running at http://localhost:8000");
});
